package com.duguo.dynamicmvc.factory.processor.controller;

import java.util.Map;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import com.duguo.dynamicmvc.factory.ControllerBuilder;
import com.duguo.dynamicmvc.factory.WorkingModel;
import com.duguo.dynamicmvc.factory.utils.ChangeOnceBoolean;
import com.duguo.dynamicmvc.factory.utils.javassist.FieldUtils;
import com.duguo.dynamicmvc.factory.utils.javassist.MethodUtils;
import com.duguo.dynamicmvc.support.ModelAndViewImpl;
import com.duguo.dynamicmvc.support.servlet.NotFoundException;
import com.duguo.dynamicmvc.view.ExceptionView;
import com.duguo.dynamicmvc.view.StaticView;
import com.duguo.dynamicmvc.view.View;
import com.duguo.dynamicmvc.view.forward.ForwardView;
import com.duguo.dynamicmvc.view.resolver.ViewResolver;

public class ControllerMethodCreatorImpl implements ControllerMethodCreator{

	ControllerNameBuilder _controllerNameBuilder;
	ControllerInterceptorCreator _controllerInterceptorCreator;
	ViewResolver _viewResolver;

    public CtClass getInternalInterface(CtMethod targetCtMethod) throws Exception {
        String internalControllerInterfaceName = _controllerNameBuilder.getControllerInternalInterfaceName(targetCtMethod);
        CtClass internalHandlerInterfaceCtClass = null;
        try {
            internalHandlerInterfaceCtClass = ClassPool.getDefault().get(internalControllerInterfaceName);
        } catch (Exception ex) {
            internalHandlerInterfaceCtClass = ClassPool.getDefault().makeInterface(internalControllerInterfaceName);
            MethodUtils.addAbstractMethodFromMethod(internalHandlerInterfaceCtClass, targetCtMethod);
            internalHandlerInterfaceCtClass.toClass();
        }
        return internalHandlerInterfaceCtClass;
    }

    public CtMethod getHanderCtMethodFromParent(String parentControllerClassName) throws Exception {
        CtClass parentControllerCtClass = ClassPool.getDefault().get(parentControllerClassName);
        CtMethod controllerHandlerCtMethod = MethodUtils.findMethod(parentControllerCtClass, "handleRequest");
        return controllerHandlerCtMethod;
    }

    public Class getStaticControllerClass(CtMethod targetCtMethod, CtClass staticModelCtClass,CtClass parentControllerCtClass,String controllerName, ControllerBuilder controllerBuilder,ControllerClassCreator controllerClassCreator) throws Exception {

        String staticControllerName = _controllerNameBuilder.getControllerInternalClassName(parentControllerCtClass.getName(), controllerName, false);

        CtClass staticControllerCtClass = ClassPool.getDefault().makeClass(staticControllerName);
        CtClass internalHandlerInterfaceCtClass = getInternalInterface(targetCtMethod);
        staticControllerCtClass.addInterface(internalHandlerInterfaceCtClass);
        CtMethod staticViewMethod=MethodUtils.addMethodFromAbstractMethod(staticControllerCtClass, targetCtMethod);
        FieldUtils.setupField(staticControllerCtClass, controllerClassCreator.getViewCtClass(), "view");
        FieldUtils.setupField(staticControllerCtClass, parentControllerCtClass, "controller");
        createStaticViewControllerHandlerMethod(staticControllerCtClass,staticViewMethod,staticModelCtClass,controllerBuilder);
        staticControllerCtClass.toClass();
        return ClassPool.getDefault().getClassLoader().loadClass(staticControllerName);
    }
    

    public void createMultiActionHandlerMethod(CtClass controllerCtClass,
                                                      CtClass controllerInternalInterface, CtMethod controllerHandlerCtMethod, boolean hasDynamicController,Map<String,Object> viewMappings, boolean isSingleAction, ControllerBuilder controllerBuilder) throws Exception {
        controllerHandlerCtMethod = MethodUtils.addMethodFromAbstractMethod(controllerCtClass, controllerHandlerCtMethod);

        StringBuffer methodBody = startMethodBody();        
        if(isSingleAction) {
        	addSingleActionHandlerCode(methodBody, controllerInternalInterface, controllerHandlerCtMethod);
        }else {
        	StringBuffer notfoundHandler = getExceptionHandlerCode(controllerCtClass,viewMappings,"404",controllerBuilder);        
            StringBuffer applicationExceptionHandler = getExceptionHandlerCode(controllerCtClass,viewMappings,"500",controllerBuilder);        
            if(notfoundHandler.length()>0 || applicationExceptionHandler.length()>0)
            	methodBody.append("try{");
            addRegistedControllerHandlerCode(methodBody, controllerInternalInterface, controllerHandlerCtMethod);
            addDynamicControllerHandleCode(hasDynamicController, methodBody, controllerHandlerCtMethod);
            addViewResolveCode(methodBody, controllerInternalInterface, controllerHandlerCtMethod);
            addExceptionHander(methodBody, notfoundHandler, applicationExceptionHandler);
        }
        endMethodBody(methodBody, controllerHandlerCtMethod);
    }


	void addExceptionHander(StringBuffer methodBody, StringBuffer notfoundHandler, StringBuffer applicationExceptionHandler) {
		if(notfoundHandler.length()>0){
        	methodBody.append("}catch("+NotFoundException.class.getName()+" ex){");
        	methodBody.append(notfoundHandler.toString());
        	methodBody.append("}");
        }
        if(applicationExceptionHandler.length()>0){
        	if(notfoundHandler.length()==0)
        		methodBody.append("}");
        	methodBody.append("catch("+Exception.class.getName()+" ex){");
        	methodBody.append(applicationExceptionHandler.toString());
        	methodBody.append("}");
        }
	}


    StringBuffer getExceptionHandlerCode(CtClass controllerCtClass,Map<String,Object> viewMappings,String code, ControllerBuilder controllerBuilder)throws Exception {
    	StringBuffer handlerCode = new StringBuffer();
    	boolean hasDynamicView=false;
    	if(viewMappings!=null && viewMappings.get(code)!=null){
    		hasDynamicView=true;
    	}
    	if(!hasDynamicView && controllerBuilder.getProcessorHolder().getViewProcessor().hasView(controllerCtClass, code,controllerBuilder)){
    		hasDynamicView=true;
    	}
    	
    	String controllerPath=_controllerNameBuilder.getControllerPath(controllerCtClass.getName());
    	View view=_viewResolver.resolve(controllerPath+code);
    	if(hasDynamicView || (view!=null && !(view instanceof StaticView))){
    		handlerCode.append(View.class.getName() + " view = new "+ForwardView.class.getName()+"(controllerBuilder.getProcessorHolder().getControllerProcessor().getControllerNameBuilder().getSameLevelPath($3,\""+controllerPath+"\")+\""+code+"\");");
    		createExceptionHandlerViewCode(code, handlerCode);
    	}else{
        	if(view==null){
        		view=_viewResolver.resolve(controllerPath+code+".html");
        	}
			if (view!=null) {
	    		handlerCode.append(View.class.getName() + " view=controllerBuilder.getViewResolver().resolve("+getControllerPathCode()+"+\""+code+".html\");");
	    		createExceptionHandlerViewCode(code, handlerCode);					
			}
    	}
		return handlerCode;
	}


	private void createExceptionHandlerViewCode(String code, StringBuffer notfoundHandler) {
		notfoundHandler.append("view=new "+ExceptionView.class.getName()+"(view,"+code+",ex);");
		notfoundHandler.append("return new "+ModelAndViewImpl.class.getName()+"(view);");
	}

	public void createDynamicHandlerMethod(CtClass controllerCtClass, CtMethod parentHandlerCtMethod, ControllerProcessor controllerProcessor) throws Exception {
        CtMethod controllerHandlerCtMethod = MethodUtils.addMethodFromAbstractMethod(controllerCtClass, parentHandlerCtMethod);
        CtMethod dynamicControllerCtMethod = controllerProcessor.findDynamicMethod(controllerCtClass);
        StringBuffer methodBody = startMethodBody();
        methodBody.append(dynamicControllerCtMethod.getReturnType().getName() + " resolvedObject=" + dynamicControllerCtMethod.getName() + "(" + getMatchedParameter(parentHandlerCtMethod, dynamicControllerCtMethod) + "$3.getCurrentServlet());");
        methodBody.append("if(resolvedObject!=null){");
        methodBody.append("$3.increasePosition();");
        methodBody.append("return " + parentHandlerCtMethod.getName() + "($$,resolvedObject);");
        methodBody.append("}");
        methodBody.append("return null;");
        endMethodBody(methodBody, controllerHandlerCtMethod);
    }


    public void createInternalMethodControllerHandlerMethod(WorkingModel workingModel, CtClass internalControllerClass, CtMethod controllerMethodCtMethod, ControllerBuilder controllerBuilder)
            throws Exception {
        CtMethod controllerMethod = MethodUtils.addMethodFromAbstractMethod(internalControllerClass, workingModel.getControllerHandlerCtMethod());
        StringBuffer methodBody = startMethodBody();
        String returnText;
        if(controllerMethodCtMethod.getParameterTypes().length==1) {
        	returnText="controller." + controllerMethodCtMethod.getName() + "(model);";
        }else {
    		methodBody.append(View.class.getName()+" returnView=null;");
        	returnText="returnView=controller." + controllerMethodCtMethod.getName() + "(model,view);";
        }
        ChangeOnceBoolean hasValidationException=new ChangeOnceBoolean(false);
        returnText=_controllerInterceptorCreator.addInterceptors(internalControllerClass,controllerMethodCtMethod, returnText,hasValidationException,controllerBuilder);
        CtClass modelCtClass=controllerBuilder.getProcessorHolder().getModelProcessor().makeControllerModelImpl(workingModel.getControllerFactory(),internalControllerClass,workingModel.getControllerHandlerCtMethod(),controllerMethodCtMethod);
        controllerBuilder.getProcessorHolder().getModelProcessor().addModelBindCode(internalControllerClass,methodBody, modelCtClass,controllerMethod,returnText,hasValidationException,controllerBuilder);
        addRetureMV(methodBody);
        endMethodBody(methodBody, controllerMethod);
    }


    public void createStaticViewControllerHandlerMethod(CtClass controllerCtClass,CtMethod handlerCtMethod, CtClass staticModelCtClass, ControllerBuilder controllerBuilder) throws Exception {
        StringBuffer methodBody = startMethodBody();
		methodBody.append(View.class.getName()+" returnView=view;");
        ChangeOnceBoolean hasValidationException=new ChangeOnceBoolean(false);
        String returnText=_controllerInterceptorCreator.addInterceptors(controllerCtClass,handlerCtMethod, "",hasValidationException,controllerBuilder);
        controllerBuilder.getProcessorHolder().getModelProcessor().addModelBindCode(controllerCtClass,methodBody, staticModelCtClass,handlerCtMethod,returnText,hasValidationException,controllerBuilder);
        addRetureMV(methodBody);
        endMethodBody(methodBody, handlerCtMethod);
    }


	void addRetureMV(StringBuffer methodBody) {
		if(methodBody.indexOf("returnView=")>0) {
			methodBody.append("return new " + ModelAndViewImpl.class.getName() + "(model,returnView);");			
		}else {
			methodBody.append("return new " + ModelAndViewImpl.class.getName() + "(model,view);");
		}
	}

    public void createControllerRegisterMethod(CtClass targetClass) throws Exception {
        StringBuffer methodBody = new StringBuffer("public void ");
        methodBody.append("registerController");
        methodBody.append("(String controllerName,Object controllerInstance) throws Exception ");
        methodBody.append("{");
        methodBody.append("controllerBuilder.getProcessorHolder().getRegisterProcessor().registerController(this,$$);");
        methodBody.append("}");
        MethodUtils.addMethod(targetClass, methodBody.toString());
    }

    public void createViewRegisterMethod(CtClass targetClass) throws Exception {
        StringBuffer methodBody = new StringBuffer("public void ");
        methodBody.append("registerView");
        methodBody.append("(String controllerName," + View.class.getName() + " view) throws Exception ");
        methodBody.append("{");
        methodBody.append("controllerBuilder.getProcessorHolder().getRegisterProcessor().registerView(this,$$);");
        methodBody.append("}");
        MethodUtils.addMethod(targetClass, methodBody.toString());
    }


    StringBuffer startMethodBody() {
        StringBuffer methodBody = new StringBuffer();
        methodBody.append("{");
        return methodBody;
    }


    void endMethodBody(StringBuffer methodBody, CtMethod targetCtMethod) throws Exception {
        methodBody.append("}");
        targetCtMethod.setBody(methodBody.toString());
    }

    void addRegistedControllerHandlerCode(StringBuffer methodBody, CtClass controllerInternalInterface, CtMethod controllerHandlerCtMethod) {
    	methodBody.append(controllerInternalInterface.getName()
                + " controller=(" + controllerInternalInterface.getName()
                + ")" + "registedControllers"
                + ".get($3.getCurrentServlet());");

        methodBody.append("if(controller!=null){");
        methodBody.append("$3.increasePosition();");
        methodBody.append("return controller.handleRequest($$);");
        methodBody.append("}");
    }

    void addSingleActionHandlerCode(StringBuffer methodBody, CtClass controllerInternalInterface, CtMethod controllerHandlerCtMethod) {
        methodBody.append("$3.increasePosition();");
        methodBody.append("return controller.handleRequest($$);");
    }

    void addDynamicControllerHandleCode(boolean hasDynamicController, StringBuffer methodBody, CtMethod controllerHandlerCtMethod) throws Exception{
        if (hasDynamicController) {
            methodBody.append(controllerHandlerCtMethod.getReturnType().getName() + " ");
            methodBody.append("successMV=" + "dynamicController" + "." + controllerHandlerCtMethod.getName() + "($$);");
            methodBody.append("if(successMV!=null) return successMV;");
        }
    }


    void addViewResolveCode(StringBuffer methodBody, CtClass controllerInternalInterface, CtMethod controllerHandlerCtMethod) {
        methodBody.append(View.class.getName() + " view = controllerBuilder.getViewResolver().resolve("+getControllerPathCode()+"+$3.getCurrentServlet());");
        methodBody.append("if(view!=null){");
		        methodBody.append("registerView" + "($3.getCurrentServlet(),view);");
		        methodBody.append("return " + controllerHandlerCtMethod.getName() + "($$);");
        methodBody.append("}else{");
        	methodBody.append("view = controllerBuilder.getViewResolver().resolve("+getControllerPathCode()+"+$3.getRemainPath());");
	        methodBody.append("if(view !=null){");
		    	methodBody.append("return new "+ModelAndViewImpl.class.getName()+"(view);");
	    methodBody.append("}}");
		methodBody.append("throw new "+NotFoundException.class.getName()+"();");
    }
    

    private String getControllerPathCode() {
    	return "controllerBuilder.getProcessorHolder().getControllerProcessor().getControllerNameBuilder().getControllerPath(getClass().getName())";
    }
    

    private String getMatchedParameter(CtMethod parentHandlerCtMethod, CtMethod targetCtMethod) throws Exception {
        String baseParams = null;
        if (targetCtMethod.getParameterTypes().length > 1) {
            baseParams = "";
            for (int i = 0; i < targetCtMethod.getParameterTypes().length - 1; i++) {
            	boolean founded=false;
                for (int j = 0; j < parentHandlerCtMethod.getParameterTypes().length; j++) {
                    if (targetCtMethod.getParameterTypes()[i] == parentHandlerCtMethod.getParameterTypes()[j]) {
                        baseParams += "$" + (j + 1) + ",";
                        founded=true;
                        break;
                    }
                }
                if(!founded)
                	throw new RuntimeException("No "+targetCtMethod.getParameterTypes()[i].getName()+" found from parentHandlerCtMethod");
            }
        }

        if (baseParams == null)
            return "";
        else
            return baseParams;
    }

	public ControllerNameBuilder getControllerNameBuilder() {
		return _controllerNameBuilder;
	}

	public void setControllerNameBuilder(ControllerNameBuilder controllerNameBuilder) {
		_controllerNameBuilder = controllerNameBuilder;
	}

	public ControllerInterceptorCreator getControllerInterceptorCreator() {
		return _controllerInterceptorCreator;
	}

	public void setControllerInterceptorCreator(
			ControllerInterceptorCreator controllerInterceptorCreator) {
		_controllerInterceptorCreator = controllerInterceptorCreator;
	}

	public void setViewResolver(ViewResolver viewResolver) {
		_viewResolver = viewResolver;
	}


}
